package ;
import Main;
import HLex;
import HLlr;
import neko.Lib;









class Scope {
	static var nextId:Int = -2;
	public var id:Int;
	
    public var parent:Scope;
    
    public var file  (get_file,never):Scope;
    public var object(get_object,never):Scope;
    public var method(get_method,never):Scope;
	
	public inline function get_global() {
		var p = this;
		while(p!=Main.global && p!=Main.mIndet) p = p.parent;
		return p == Main.mIndet ? null : p;
	}
	public inline function get_file() {
		var p = this;
		while(p.parent!=Main.global && p!=Main.mIndet) p = p.parent;
		return p == Main.mIndet ? null : p;
	}
	public inline function get_object() {
		var p = this;
		if(p.parent==Main.global) return null;
		else {
			while(p.parent.parent!=Main.global && p!=Main.mIndet) p = p.parent;
			return p;
		}
	}
	public inline function get_method() {
		var p = this;
		if(p.parent==Main.global) return null;
		else if(p.parent != Main.mIndet && p.parent.parent==Main.global) return null;
		else {
			while(p.parent.parent.parent!=Main.global && p!=Main.mIndet) p = p.parent;
			return p;
		}
	}
    
	public function toString() {
		if(this==Main.global) return "global";
		else if(this==Main.mIndet) return "???";
		else return "{"+id+"}->"+parent.toString();
	}
	
    public var macros:Array<Macro>;
    public var data  :Array<State>;
	
	public var locked:Bool;
    
    public function new(data:Array<State>, ?parent:Scope, ?names:Array<{macro:Macro,args:Array<String>}>, ?macs:Scope, ?mac2:String) {
		id = nextId++;
        macros = new Array<Macro>();
        this.data = new Array<State>();
		
        if(parent==null) parent = Main.global;
        this.parent = parent;

        if(names==null) names = [];
        if(mac2==null) mac2 = "";
        
        for(i in data)
            this.data.push(proc(this,i,names,macs,mac2));
    }
    
    private function proc(cur:Scope, x:State, names:Array<{macro:Macro,args:Array<String>}>, macs:Scope, mac2:String, ?argument:Bool=false) {
        
        
        return switch(x) {
            case sIdent(n):
                var nx = null;
                for(i in names) {
                    var cnt = 0;
                    for(j in i.args) {
                        if(j==n) {
                            nx = sArgument(i.macro.id,cnt);
                            break;
                        }
                        cnt++;
                    }
                    if(nx!=null) break;
                }
                if(nx!=null) nx else x;
            
            case sMixin(acc,n,args,post,_):
                var sacc =  {
			if(argument) Main.mIndet;
			else {
				var ret = if(acc==null) this;
				else switch(acc) {
					case sLocal:  this;
					case sGlobal: get_global();
					case sFile:   file;
					case sObject: object;
					case sMethod: method;
				}
				if(macs!=null) {
					var outer = macs;
					while(outer!=null && outer!=ret) outer = outer.parent;
					if(outer!=null && macs!=ret) {
						Lib.println("Macro scope not possible due to nesting");
						Lib.println("\t "+mac2+"::"+n);
					}
					
					
					
					if(macs==ret) ret = Main.mIndet;
				}
				ret;
			}
        };
				trace(n+" "+sacc);
                
                var post2 = [];
                var mac = new Macro();
                var ret = sMixin(acc,n,args,post2,mac);
                mac.init(ret);
               
                var names2 = names.copy();
                names2.unshift({macro:mac, args:args});
                for(i in post)
                    post2.push(proc(Main.mIndet,i,names2,cur,mac2+"::"+n));
                    
				mac.owner = sacc;
                if(sacc!=null)
                    sacc.macros.push(mac);
                else
                    neko.Lib.println("Impossible macro scope");
                    
                ret;
                
            case sExpand(acc,n,args,post,_):
                var sacc =  {
			if(argument) Main.mIndet;
			else {
				var ret = if(acc==null) this;
				else switch(acc) {
					case sLocal:  this;
					case sGlobal: get_global();
					case sFile:   file;
					case sObject: object;
					case sMethod: method;
				}
				if(macs!=null) {
					var outer = macs;
					while(outer!=null && outer!=ret) outer = outer.parent;
					if(outer!=null && macs!=ret) {
						Lib.println("Macro scope not possible due to nesting");
						Lib.println("\t "+mac2+"::"+n);
					}
					
					
					
					if(macs==ret) ret = Main.mIndet;
				}
				ret;
			}
        };
				trace(n+" "+sacc);
                
                var post2 = [];
                var mac = new Macro();
                var ret = sExpand(acc,n,args,post2,mac);
                mac.init(ret);
               
                var names2 = names.copy();
                names2.unshift({macro:mac, args:args});
                for(i in post)
                    post2.push(proc(Main.mIndet,i,names2,cur,mac2+"::"+n));
                
				mac.owner = sacc;
                if(sacc!=null)
                    sacc.macros.push(mac);
                else
                    neko.Lib.println("Impossible macro scope");
                    
                ret;
                
            case sDefine(acc,newfile,pre,n,args,post,_):
                var sacc =  {
			if(argument) Main.mIndet;
			else {
				var ret = if(acc==null) this;
				else switch(acc) {
					case sLocal:  this;
					case sGlobal: get_global();
					case sFile:   file;
					case sObject: object;
					case sMethod: method;
				}
				if(macs!=null) {
					var outer = macs;
					while(outer!=null && outer!=ret) outer = outer.parent;
					if(outer!=null && macs!=ret) {
						Lib.println("Macro scope not possible due to nesting");
						Lib.println("\t "+mac2+"::"+n);
					}
					
					
					
					if(macs==ret) ret = Main.mIndet;
				}
				ret;
			}
        };
				trace(n+" "+sacc);
                
                var post2 = [];
                var mac = new Macro();
                var ret = sDefine(acc,newfile,pre,n,args,post2, mac);
                mac.init(ret);
                               
                var names2 = names.copy();
                names2.unshift({macro:mac, args:args});
                for(i in post)
                    post2.push(proc(Main.mIndet,i,names2,cur,mac2+"::"+n));
                
				mac.owner = sacc;
                if(sacc!=null)
                    sacc.macros.push(mac);
                else
                    neko.Lib.println("Impossible macro scope");
                    
                ret;
                
            case sScope(scope):
                sRealScope(new Scope(scope,cur,names,macs,mac2));
                
            case sCall(xs):
                var xs2 = [];
                for(i in xs) {
                    var t = [];
                    for(j in i) t.push(proc(Main.mIndet,j,names,macs,mac2,true));
                    xs2.push(t);
                }

                sCall(xs2);

            default:
                x;
        };
    }
    
    public inline function macros_in_scope() {
        var ret = new Array<Macro>();
        var cur = this;
        while(cur!=null && cur!=Main.mIndet && cur!=Main.global) {
            for(i in cur.macros) {
                var found = false;
                for(j in ret) {
                    if(i.argc==j.argc && i.name==j.name) {
                        found = true;
                        break;
                    }
                }
                if(!found)
                    ret.push(i);
            }
            cur = cur.parent;
        }
        return ret;
    }
}
